"""
@-*- coding: utf-8 -*-
@ python: 3.12.3
@ 创建者: JacksonCode
@ 创建时间: 2025-11-01
"""
# -*- coding: utf-8 -*-
"""
PalmPay 提现代付接口调用示例 (Python)
支持签名生成 + POST 请求发送
"""

import requests
import json
import time
import uuid
import base64
import hashlib
from typing import Dict, Any
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend


def generate_unique_order_id() -> str:
    """生成32位唯一订单号"""
    return uuid.uuid4().hex[:32]


def generate_nonce_str(length: int = 32) -> str:
    """生成指定长度的随机字母数字字符串"""
    import string
    import random
    letters = string.ascii_letters + string.digits
    return ''.join(random.choice(letters) for _ in range(length))


def get_signature(
    pob_user: Dict[str, Any],
    timestamp: int,
    order_id: str,
    dto: Dict[str, Any],
    nonce_str: str,
    palm_config: Dict[str, str]
) -> str:
    """
    生成 PalmPay 所需的 Signature
    使用 RSA 私钥对排序后的参数进行 SHA1WithRSA 签名，并 Base64 编码
    """
    # 构建 TreeMap（按 key 字典序排序）
    sorted_map = {}
    sorted_map['amount'] = dto['amount']
    sorted_map['currency'] = dto['currency']
    sorted_map['nonceStr'] = nonce_str
    sorted_map['notifyUrl'] = palm_config['notifyUrlPrefix'] + \
                              palm_config['notifyUrl'].replace("bee-palmPay", "bee-palmPayWithBank")
    sorted_map['orderId'] = order_id
    sorted_map['payeeBankAccNo'] = dto['bankAccNo']
    sorted_map['payeeBankCode'] = dto['bankCode']
    sorted_map['payeeName'] = pob_user['userName']
    sorted_map['payeePhoneNo'] = dto['phoneNumber']
    sorted_map['requestTime'] = str(timestamp)  # 必须是字符串
    sorted_map['version'] = 'V1.1'

    print("sortedMap 参数：", json.dumps(sorted_map, indent=2, ensure_ascii=False))

    # 按 key 字典序升序拼接成: key1=value1&key2=value2...
    to_sign_parts = []
    for key in sorted(sorted_map.keys()):
        value = sorted_map[key]
        to_sign_parts.append(f"{key}={value}")
    to_sign_string = "&".join(to_sign_parts)

    print("待签名字符串: ", to_sign_string)

    # 加载 PEM 格式的私钥
    try:
        private_key = serialization.load_pem_private_key(
            palm_config['privateKey'].encode('utf-8'),
            password=None,
            backend=default_backend()
        )
    except Exception as e:
        raise ValueError(f"加载私钥失败，请检查格式: {e}")

    # 使用 SHA1 + RSA PKCS#1 v1.5 进行签名（即 SHA1WithRSA）
    try:
        signature_bytes = private_key.sign(
            to_sign_string.encode('utf-8'),
            padding.PKCS1v15(),
            hashes.SHA1()
        )
    except Exception as e:
        raise Exception(f"RSA 签名失败: {e}")

    # Base64 编码
    signature_b64 = base64.b64encode(signature_bytes).decode('utf-8')
    return signature_b64


def build_request_body(
    pob_user: Dict[str, Any],
    order_id: str,
    timestamp: int,
    dto: Dict[str, Any],
    nonce_str: str
) -> Dict[str, Any]:
    """构造请求体数据"""
    return {
        "orderId": order_id,
        "requestTime": timestamp,
        "nonceStr": nonce_str,
        "version": "V1.1",
        "amount": dto["amount"],
        "currency": dto["currency"],
        "payeeName": pob_user["userName"],
        "payeePhoneNo": dto["phoneNumber"],
        "payeeBankCode": dto["bankCode"],
        "payeeBankAccNo": dto["bankAccNo"],
        "notifyUrl": dto.get("notifyUrl")  # 可选覆盖
    }


# ==================== 配置区（请替换为你的实际值） ====================

# PalmPay 配置（模拟 palmConfig）
palm_config = {
    "merchantCode": "YOUR_MERCHANT_CODE",  # 替换为你的商户号
    "notifyUrlPrefix": "https://yourdomain.com/",
    "notifyUrl": "callback/bee-palmPay/withdraw",
    "privateKey": """-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7UvLKvR...
...（请替换为你的真实私钥，保持 BEGIN/END 行）
-----END PRIVATE KEY-----"""
}

# 用户信息
pob_user = {
    "userName": "John Doe"  # 提现人姓名
}

# 提现请求参数（模拟 WithDrawlDto）
dto = {
    "amount": "100.00",
    "currency": "NGN",
    "bankAccNo": "1234567890",
    "bankCode": "058",  # 示例：GTBank 是 058
    "phoneNumber": "+2348123456789"
}

# ==================== 主流程 ====================

if __name__ == "__main__":
    print("开始 PalmPay 代付请求...")

    # 1. 生成必要参数
    order_id = generate_unique_order_id()
    timestamp = int(time.time() * 1000)  # 毫秒时间戳
    nonce_str = generate_nonce_str(32)

    print(f"订单ID: {order_id}")
    print(f"时间戳: {timestamp}")
    print(f"随机串: {nonce_str}")

    # 2. 生成签名
    try:
        signature = get_signature(pob_user, timestamp, order_id, dto, nonce_str, palm_config)
        if not signature:
            raise RuntimeError("获取签名失败")
        print(f"✅ 生成签名成功: {signature[:64]}...")  # 打印前64位
    except Exception as e:
        print(f"❌ 获取签名失败: {e}")
        exit(1)

    # 3. 构造请求体
    request_data = build_request_body(pob_user, order_id, timestamp, dto, nonce_str)
    json_data = json.dumps(request_data, separators=(',', ':'))  # 紧凑格式
    print(f"请求数据: {json_data}")

    # 4. 发送 POST 请求
    url = "https://open-gw-daily.palmpay-inc.com/api/v2/merchant/payment/payout"

    headers = {
        "Accept": "application/json, text/plain, */*",
        "CountryCode": "NG",
        "Authorization": f"Bearer {palm_config['merchantCode']}",
        "Signature": signature,
        "Content-Type": "application/json"
    }

    print(f"\n📤 正在请求: {url}")
    try:
        response = requests.post(url, headers=headers, data=json_data, timeout=30)
        print(f"📡 HTTP {response.status_code}")

        try:
            resp_json = response.json()
        except json.JSONDecodeError:
            resp_json = {"raw_response": response.text}

        print("✅ Palm订单创建响应:")
        print(json.dumps(resp_json, indent=2, ensure_ascii=False))

    except requests.exceptions.Timeout:
        print("❌ 请求超时")
    except requests.exceptions.ConnectionError:
        print("❌ 网络连接错误")
    except requests.exceptions.RequestException as e:
        print(f"❌ 请求异常: {e}")
        if hasattr(e, 'response') and e.response is not None:
            print(e.response.text)